package fr.pud.tests;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.openstreetmap.gui.jmapviewer.Coordinate;

import fr.pud.client.model.PUDStation;
import fr.pud.server.OSRMTools;

/**
 * This class implements tests for computing distances between points.
 * The file need is "tests/coordonnees.csv", which will normally contain
 * coordinates data (i.e. latitudes and longitudes).
 * 10 avr. 2012 - tsp-osm
 * @author Simon Devineau, Adrien Hierry, Leopold Houdin, Pierre Reliquet
 *         Ecole des Mines de Nantes
 *         Tests.java
 */
public class Tests {
    /**
     * Computes the average of the time needed to compute the distance matrix.
     * @param matrix
     *            the matrix whom the sum is to be calculated.
     * @return the average.
     */
    public static double computeAvg(double[][][] matrix) {
        double sum = 0.0;
        double count = 0.0;
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix.length; j++) {
                if (matrix[i][j][0] != 0.0) {
                    sum += matrix[i][j][1];
                    count += 1.0;
                }
            }
        }
        return sum / count;
    }

    /**
     * Computes the distance between two points (i.e. two coordinates).
     * Be careful, the path returned is oriented (i.e. path(a,b)!=path(b,a)).
     * @param a
     *            the first coordinate.
     * @param b
     *            the second coordinate.
     * @return the distance between the two coordinates.
     */
    public static double computeDistanceBetween2Coordinates(Coordinate a,
            Coordinate b) {
        try {
            BufferedReader buffer = new BufferedReader(new InputStreamReader(
                    new URL(OSRMTools.SERVER + OSRMTools.VIAROUTE
                            + OSRMTools.START + OSRMTools.EQUAL + a.getLat()
                            + OSRMTools.COMMA + a.getLon()
                            + OSRMTools.AMPERSAND + OSRMTools.DEST
                            + OSRMTools.EQUAL + b.getLat() + OSRMTools.COMMA
                            + b.getLon()).openStream()));
            String line = null;
            while ((line = buffer.readLine()) != null) {
                Matcher m = Pattern.compile(
                        OSRMTools.REGEX_ANYTHING + OSRMTools.REGEX_TIME_OPEN
                                + OSRMTools.REGEX_NUMBER
                                + OSRMTools.REGEX_TIME_CLOSE
                                + OSRMTools.REGEX_ANYTHING).matcher(line);
                if (m.matches()) {
                    buffer.close();
                    return Double.parseDouble(m.group(1));
                }
            }
            buffer.close();
        }
        catch (MalformedURLException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return 0.0;
    }

    /**
     * Computes the distance matrix with ORSM server.
     * @param nodes
     *            coordinates array.
     * @return the distance matrix.
     */
    public static double[][][] computeDistanceMatrix(ArrayList<PUDStation> nodes) {
        int size = nodes.size();
        double[][][] matrix = new double[size][size][2];
        long tmp = 0, max = size * size;
        long matrixStart = new Date().getTime();
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                long coordStart = new Date().getTime();
                if (i != j) {
                    matrix[i][j][0] = OSRMTools
                            .computeDistanceBetween2Coordinates(nodes.get(i)
                                    .getAddress().getCoordinate(), nodes.get(j)
                                    .getAddress().getCoordinate());
                }
                else {
                    matrix[i][j][0] = 0.0;
                }
                long coordEnd = new Date().getTime();
                matrix[i][j][1] = coordEnd - coordStart;
                tmp++;
                System.out.println("\t" + tmp + "/" + max);
            }
        }
        long matrixEnd = new Date().getTime();
        System.out.println("Matrix computed in "
                + OSRMTools.getDurationString(matrixEnd - matrixStart));
        return matrix;
    }

    /**
     * Computes the sum of the time needed to compute the distance matrix.
     * @param matrix
     *            the matrix whom the sum is to be calculated.
     * @return the sum.
     */
    public static double computeSum(double[][][] matrix) {
        double sum = 0.0;
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix.length; j++) {
                sum += matrix[i][j][1];
            }
        }
        return sum;
    }

    /**
     * Loads the data file and stores coordinates in an array.
     * @param filename
     *            the file name.
     * @return an array containing the coordinates.
     */
    public static ArrayList<PUDStation> loadFile(String filename) {
        ArrayList<PUDStation> nodes = new ArrayList<PUDStation>();
        try {
            BufferedReader buffer = new BufferedReader(new FileReader(new File(
                    filename)));
            String line = null;
            while ((line = buffer.readLine()) != null) {
                String[] tab = line.split(";");
                if (tab.length == 2) {
                    nodes.add(new PUDStation(new Coordinate(Double
                            .parseDouble(tab[0]), Double.parseDouble(tab[1]))));
                }
            }
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        catch (NumberFormatException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return nodes;
    }

    /**
     * Main method.
     * Launches computation, creates and writes computation files.
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("Reading data file... ");
        ArrayList<PUDStation> nodes = Tests.loadFile("tests/coordonnees.csv");
        System.out.println("Computing distance matrix... ");
        double[][][] matrix = Tests.computeDistanceMatrix(nodes);
        try {
            System.out.println("Creating files... ");
            File dataFile = new File("tests/data.csv");
            if (!dataFile.exists()) {
                dataFile.createNewFile();
            }
            File timeFile = new File("tests/time.csv");
            if (!timeFile.exists()) {
                timeFile.createNewFile();
            }
            BufferedWriter data = new BufferedWriter(new FileWriter(dataFile));
            BufferedWriter time = new BufferedWriter(new FileWriter(timeFile));
            System.out.println("Writing header... ");
            time.write("\"Sum :\";" + Tests.computeSum(matrix) + ";\n");
            time.write("\"Avg :\";" + Tests.computeAvg(matrix) + ";\n");
            System.out.println("Writing files... ");
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix.length; j++) {
                    data.write(matrix[i][j][0] + ";");
                    time.write(matrix[i][j][1] + ";");
                }
                data.write("\n");
                time.write("\n");
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}
